# First hardware project ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- This tutorial walks you through two introductory hardware projects with the Quectel Pi H1 development kit: capturing a photo with an IMX219 camera and monitoring the onboard buttons. # Capture your first photo with an IMX219 camera If you do not have an IMX219 camera, you can skip this chapter. ## Install the camera 1. Power off the Quectel Pi H1 smart single-board computer. 2. Locate the MIPI CSI ribbon cable connector labeled **CAMERA1** or **CAMERA2** on the board. 3. Insert the IMX219 camera ribbon cable into the connector with its contacts facing down, and secure the latch. 4. Reconnect the power supply and boot the board. ```{note} Do not insert the ribbon cable backward. Its contacts must face the circuit board (downward). Always disconnect the power before installing or removing the ribbon cable. ``` ```{image} images/image_Su1tb3Kdxo9sRRxmG75ctitunbP.webp :width: 910px :height: 508px ``` ## Capture a photo with the camera ### Configure the environment Before running the photo capture command, set the plugin path (you must set it again each time you open a new terminal window): ```bash export GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0:$GST_PLUGIN_PATH ``` ### Capture a photo Run the following command to capture frames and save them as JPEG files: *max-files* controls both the number of photos captured and the number of files saved locally. With *max-files=1*, the command captures one photo and saves one JPEG file in */home/pi*. Setting *max-files=n* captures *n* photos and saves *n* local files. ```bash sudo -E gst-launch-1.0 -e \ qtiqmmfsrc name=camsrc ! \ 'video/x-raw,format=NV12,width=1280,height=720,framerate=30/1' ! \ tee name=t ! \ queue ! videoconvert ! jpegenc ! \ multifilesink location=/home/pi/shot-%05d.jpg max-files=1 ``` When the pipeline state changes to *PLAYING*, image capture is active. Press "Ctrl+C" to stop the pipeline. ```{note} To capture *n* photos and save *n* image files locally, set *max-files=n*. ``` ## Advanced: Capture a photo with Python and OpenCV Install OpenCV: ```bash sudo apt update sudo apt install -y python3-opencv ``` Create the file *~/capture.py*: ```python import cv2 # Access the MIPI camera through a GStreamer pipeline gst_pipeline = ( "qtiqmmfsrc name=camsrc ! " "video/x-raw,format=NV12,width=1280,height=720,framerate=30/1 ! " "videoconvert ! video/x-raw,format=BGR ! appsink" ) cap = cv2.VideoCapture(gst_pipeline, cv2.CAP_GSTREAMER) if not cap.isOpened(): print("Error: Unable to open the camera") exit(1) # Skip the first few frames to allow automatic exposure and white balance to settle for i in range(30): cap.read() ret, frame = cap.read() if ret: cv2.imwrite("/home/pi/opencv_photo.jpg", frame) print("Photo captured successfully and saved to /home/pi/opencv_photo.jpg") else: print("Error: Unable to read an image frame") cap.release() ``` ```{note} The script saves the photo to */home/pi/opencv_photo.jpg*. Edit this path to save the file elsewhere. ``` Run the script (requires *sudo* privileges): ```bash sudo -E python3 ~/capture.py ``` ```{note} The IMX219 is a MIPI CSI camera and does not support the standard V4L2 interface. OpenCV must therefore access it through a GStreamer pipeline. Before running the script, ensure that you have set *export GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0:$GST_PLUGIN_PATH*. ``` # Explore GPIO ## View GPIO pin status Install the GPIO command-line tools: ```bash sudo apt install -y gpiod ``` List all GPIO controllers in the system: ```bash sudo gpiodetect ``` View the status of all pins on a specific controller: ```bash sudo gpioinfo --chip gpiochip4 ``` Each output line represents a GPIO pin. A pin marked with *consumer=xxx* is in use by a kernel driver. ```{note} The onboard **KEY1** and **KEY2** buttons are managed by the kernel's *gpio-keys* and *pmic_resin* drivers and are exposed to user space as standard Linux input devices. Therefore, use *evtest* or *evdev* to read button events instead of manipulating GPIO pins directly. ``` ## Verify the buttons with evtest Quectel Pi H1 smart single-board computer provides two user buttons, **KEY1** and **KEY2**, which are exposed through the Linux input subsystem. Event device numbers can change between system versions. Run *sudo evtest* first to identify the *event* devices for *gpio-keys* and *pmic_resin*; the table below shows the mapping used in this example. | **Button** | **Input device** | **Mapped key code** | | --- | --- | --- | | KEY1 | */dev/input/event3* | KEY_VOLUMEDOWN (114) | | KEY2 | */dev/input/event1* | KEY_VOLUMEUP (115) | Install the *evtest* utility: ```bash sudo apt install -y evtest ``` Run the following command to monitor **KEY1**: ```bash sudo evtest /dev/input/event3 ``` Press KEY1. The terminal displays the button events in real time: ```plaintext Event: time ..., type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 1 ← Pressed Event: time ..., type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 0 ← Released ``` Press "Ctrl+C" to exit. Similarly, you can use *sudo evtest /dev/input/event1* to monitor KEY2. ```{image} images/image_SBz5bA8f1oGBPnxLzLUceLklnxc.webp :width: 703px :height: 367px ``` ## Monitor button events with Python Install the *evdev* library: ```bash pip install evdev ``` Create the file *~/button_monitor.py*: ```python from evdev import InputDevice, ecodes import selectors dev_key1 = InputDevice("/dev/input/event3") dev_key2 = InputDevice("/dev/input/event1") print(f"Monitoring KEY1: {dev_key1.name}") print(f"Monitoring KEY2: {dev_key2.name}") print("Press Ctrl+C to exit") sel = selectors.DefaultSelector() sel.register(dev_key1, selectors.EVENT_READ) sel.register(dev_key2, selectors.EVENT_READ) try: while True: for key, _ in sel.select(): device = key.fileobj for event in device.read(): if event.type == ecodes.EV_KEY and event.value == 1: if event.code == ecodes.KEY_VOLUMEDOWN: print("KEY1 pressed!") elif event.code == ecodes.KEY_VOLUMEUP: print("KEY2 pressed!") except KeyboardInterrupt: print("\nMonitoring stopped") ``` Run the script: ```bash sudo -E python3 ~/button_monitor.py ``` Press KEY1 or KEY2. The terminal displays the corresponding message. Press "Ctrl+C" to exit. ```{note} Accessing Linux input devices requires *root* privileges, so run the script with *sudo*. ``` ```{image} images/image_DWS2bsCoYoSBBBxmo3NclSoznYb.webp :width: 1250px :height: 519px :align: center ``` # Troubleshooting **Q: The photo capture command reports "no element qtiqmmfsrc"** Set the plugin path first: ```bash export GST_PLUGIN_PATH=/usr/lib/gstreamer-1.0:$GST_PLUGIN_PATH ``` Then run the photo capture command. **Q: The photo capture command reports "Failed to connect to bus"** Ensure that the command includes the ***sudo -E*** prefix because the camera plugin requires ***root*** privileges. **Q:*evtest*****does not display button events** The device numbers may differ from those in the examples. Run ***sudo evtest*** without arguments to list all input devices. Then locate the *gpio-keys* and *pmic_resin* devices and use their corresponding *event* numbers. **Q: The Python script reports a permission error** Both input devices and the camera require *root* privileges: - Button script: *sudo -E python3 ~/button_monitor.py* - Photo capture script: *sudo -E python3 ~/capture.py*